3

I have a class component which uses two contexts with the value generated from API calls to a REST API.

What I want to do is get the context values and use them to update my component state.

I'm passing the context values like so

 <TextContext.Consumer>
        {(textContext) => (
          <UserContext.Consumer>

            {(userConsumer) => {
              const text = textContext.text;
              const user = userConsumer.user;

              if(text != null && user != null){
                return (

                  <div className="md:flex max-w-2xl">

                    <div className="flex flex-col flex-1 md:pr-32">

                      <FuseAnimateGroup
                        enter={{
                          animation: "transition.slideUpBigIn"
                        }}
                      >
                        <div style={{paddingRight:"8px"}}>
                          <Typography variant="h4" >{text.TITLE_PAGE_PROFILE}</Typography>
                          <TextField
                            id="outlined-full-width"
                            style={{ margin: 8 }}
                            placeholder={text.PROFILE_EMAIL_PLACEHOLDER}
                            value = {user.email}
                            disabled
                            fullWidth
                            margin="normal"
                            variant="outlined"
                            InputProps={{
                              endAdornment: (
                                <InputAdornment>
                                  <IconButton>
                                    <EmailIcon/>
                                  </IconButton>
                                </InputAdornment>
                              )
                            }}
                          />
                        </div>


                        <div style={{paddingRight:"8px"}}>
                          <form className={classes.container} noValidate autoComplete="off">
                            <TextField
                              id="outlined-full-width"
                              style={{ margin: 8 }}
                              placeholder={text.PROFILE_NAME}
                              value={user.name_user}
                              fullWidth
                              margin="normal"
                              variant="outlined"
                              InputProps={{
                                endAdornment: (
                                  <InputAdornment position="start">
                                    <AccountCircle />
                                  </InputAdornment>
                                )
                              }}
                            />


                          </form>
                        </div>
                        <div style={{paddingRight:"8px"}}>
                          <TextField
                            id="outlined-full-width"
                            style={{ margin: 8 }}
                            value={user.address_user}
                            placeholder={text.PROFILE_ADDRESS_PLACEHOLDER}
                            fullWidth
                            margin="normal"
                            variant="outlined"
                            InputLabelProps={{
                              shrink: true,
                            }}
                          />
                        </div>

                        <div style={{paddingRight:"8px"}}>
                          <form className={classes.container} noValidate autoComplete="off">
                            <TextField
                              id="outlined-full-width"
                              style={{ margin: 8 }}
                              value={user.city_user}
                              label={text.PROFILE_CITY_PLACEHOLDER}
                              className={classes.textField}
                              fullWidth
                              margin="normal"
                              variant="outlined"
                              InputProps={{
                                endAdornment: (
                                  <InputAdornment position="start">
                                    <LocationCityIcon/>
                                  </InputAdornment>
                                )
                              }}
                            />


                          </form>
                        </div>

                        <div>
                          <TextField
                            id="outlined-select-currency"
                            select
                            value={user.country_user}
                            label={text.PROFILE_COUNTRY_PLACEHOLDER}
                            InputProps={{
                              endAdornment: (
                                <InputAdornment>
                                  <IconButton>
                                    <FlagIcon/>
                                  </IconButton>
                                </InputAdornment>
                              )
                            }}
                            fullWidth
                            style={{ margin: 8, paddingRight: 8}}
                            SelectProps={{
                              MenuProps: {
                                className: classes.menu,
                              },
                            }}


                            margin="normal"
                            variant="outlined"
                          />
                        </div>
                        <div style={{padding:"10px"}}>
                          <Fab variant="contained" aria-label="delete" className={classes.fab}>

                            {text.PROFILE_CHANGE_PASSWORD_BUTTON_PLACEHOLDER}
                          </Fab>
                        </div>

                        <div style={{paddingRight:"8px"}}>
                          <Typography variant="h4" > {text.COMPANY_INFORMATION_TITLE}</Typography>
                          <TextField
                            id="outlined-full-width"
                            style={{ margin: 8 }}
                            placeholder={text.COMPANY_NAME_PLACEHOLDER}
                            value={user.name_company}
                            fullWidth
                            margin="normal"
                            variant="outlined"
                            InputLabelProps={{
                              shrink: true,
                            }}
                          />
                        </div>

                        <div style={{paddingLeft:"10px"}}>
                          <form className={classes.container} noValidate autoComplete="off">
                            <TextField
                              style={divStyle}
                              id="outlined"
                              label={text.COMPANY_EU_VAT_PLACEHOLDER}
                              value={user.vat_company}

                              className={classes.textField}
                              margin="normal"
                              variant="outlined"
                            />

                            <TextField
                              style={div2Style}
                              id="outlined"
                              label={text.COMPANY_NUMBER_PLACEHOLDER}
                              value={user.registration_number_company}
                              className={classes.textField}
                              margin="normal"
                              variant="outlined"
                            />
                          </form>
                        </div>
                        <div style={{paddingRight:"8px"}}>
                          <TextField
                            id="outlined-full-width"
                            style={{ margin: 8 }}
                            value={user.address_company}
                            placeholder={text.COMPANY_ADDRESS_PLACEHOLDER}
                            fullWidth
                            margin="normal"
                            variant="outlined"
                            InputLabelProps={{
                              shrink: true,
                            }}
                          />
                        </div>

                        <div style={{paddingRight:"8px"}}>
                          <form className={classes.container} noValidate autoComplete="off">
                            <TextField
                              id="outlined-full-width"
                              style={{ margin: 8 }}
                              label={text.COMPANY_CITY_PLACEHOLDER}
                              value={user.city_company}
                              className={classes.textField}
                              fullWidth
                              margin="normal"
                              variant="outlined"
                              InputProps={{
                                endAdornment: (
                                  <InputAdornment position="start">
                                    <LocationCityIcon/>
                                  </InputAdornment>
                                )
                              }}
                            />
                          </form>
                        </div>
                        <div>
                          <TextField
                            id="outlined-select-currency"
                            select
                            label={text.COMPANY_COUNTRY_PLACEHOLDER}
                            fullWidth
                            style={{ margin: 8, paddingRight: 8}}
                            SelectProps={{
                              MenuProps: {
                                className: classes.menu,
                              },
                            }}
                            InputProps={{
                              endAdornment: (
                                <InputAdornment>
                                  <IconButton>
                                    <FlagIcon/>
                                  </IconButton>
                                </InputAdornment>
                              )
                            }}

                            margin="normal"
                            variant="outlined"
                          />
                        </div>
                      </FuseAnimateGroup>

                    </div>

                    <div className="flex flex-col md:w-320">
                      <FuseAnimateGroup
                        enter={{
                          animation: "transition.slideUpBigIn"
                        }}
                      >
                        <Button variant="contained"  size="large" color="default" className={classes.button}>
                          {text.UPDATE_BUTTON_TEXT}
                        </Button>

                      </FuseAnimateGroup>
                    </div>
                  </div>
                );
              } else return <div>Loading...</div>
            }
            }

          </UserContext.Consumer>
        )}
      </TextContext.Consumer>

I've tried to update the state inside the render by doing something like this

  <TextContext.Consumer>
        {(textContext) => (
          <UserContext.Consumer>

            {(userConsumer) => {
              const text = textContext.text;
              const user = userConsumer.user;
              this.setState({
                user:user,
                text: text,
              })

          </UserContext.Consumer>
        )}
      </TextContext.Consumer>

The problem with this approach is that it throws the "Maximum update depth exceeded." error.

How should I go about this?

Octavian
  • 623
  • 2
  • 6
  • 17
  • The way to go about this is to write a component that you pass both context values you are interested which then updates its state. You can't call `setState` in the render method as this triggers another render which will trigger another render leading to an inifinite update loop. But why would you want store that in a local state if you already get it through the context? Its totally fine for components to not have any state and get its data from outside. – trixn Sep 25 '19 at 09:14
  • Because I want to be able to update the user fields in the back end using another API call. – Octavian Sep 25 '19 at 09:22
  • So you want to use those as initial values to a form for updating the user data? Then you should extract a component that renders that form taking the initial data as a prop. – trixn Sep 25 '19 at 09:24

3 Answers3

1

"Maximum update depth exceeded." error.

Do not setState() inside render().

How should I go about this?

Simply extract a component from it.

const User = (props) => {
  return (
    <>
      <span>{props.user}</span>
      <span>{props.text}</span>
    </>
  );
}

// in render
<TextContext.Consumer>
  {(textContext) => (
    <UserContext.Consumer>
      {(userConsumer) => (
        <User
          text={textContext.text}
          user={userConsumer.user}
        />
      ))}
    </UserContext.Consumer>
  )}
</TextContext.Consumer>

<User /> will still re-render every time the props (user, text) changes.

Joseph D.
  • 11,804
  • 3
  • 34
  • 67
0

you can't update the state inside the render function.

Like that, you will be in the infinity loop of renders. Whenever you change the state that triggers the render function then you change the state again and so on.

anyway, you don't need to store this state inside the local state to use it, you can use it directly from the context.

0

First of all - are you sure you really need to store context in state? I don't see any reason to copy context (which always available) to state. Just use values from context, not from state.

But if you really need it, you can't update state in render function, because it will cause the infinite update loop. There some options to do so:

  • Extract component:
return (
  <TextContext.Consumer>
    {({ text }) => (
      <UserContext.Consumer>
        ({user}) => <ExtractedComponent text={text} user={user} />
      </UserContext.Consumer>
    )}
  </TextContext.Consumer>
);

Then you just need to overrider getDerrivedStateFromProps() for ExtractedComponent to get new state when props changed.

  • [ugly way] perform conditional update in render function to prevent infinite loop:
if (state.user !== user || state.text !== text) {
  this.setState({ user, text });
}
  • Probably you can switch to functional components with hooks:
const YourComponent = () => {
  const { user } = useContext(UserContext);
  const { text } = useContext(TextContext);
  const [ state, setState ] = useState({});

  useEffect(() => {
    setState({ user, text });
  }, [ user, text ]);
}
Andres
  • 967
  • 6
  • 7