18

I'm creating a react game app and I want to pass state across multiple components. For that purpose I'm trying the react context api for the first time. So this is my GameContext.js

import React, { useState, createContext } from 'react';

const GameContext = createContext();

const GameProvider = ({ children }) => {
  const [name, setName] = useState('');
  const [color, setColor] = useState('');
  const [startgame, setStartgame] = useState(false);


  return (
    <GameContext.Provider value={[name, setName]}>
      {children}
    </GameContext.Provider>
  );
};

export { GameContext, GameProvider };

And I'm able to access name in the child component using

import { GameContext } from '../../context/GameContext';    
const [name, setName] = useContext(GameContext);
console.log(name);

But now I want to get the other state values into the same child component, like [color, setColor] and [startgame, setStartgame], from the GameContext.js.
How do I get those values into a child component?

I have another question, Somehow I feel this is a really stupid question, but why can't I do something like this in the GameContext.js?...

<GameContext.Provider value={[name, setName,color, setColor, startgame, setStartgame]}>

and get the values in the child component like this...

const [name, setName,color, setColor, startgame, setStartgame] = useContext(GameContext);

I tried this, but the browser is complaining that I'm breaking rules of react hooks.

anoop chandran
  • 1,460
  • 5
  • 23
  • 42
  • Did you tried ```value={{name, setName,color, setColor, startgame, setStartgame}}```? And destruture those in child component. – Niyas Nazar Mar 26 '20 at 12:17

5 Answers5

39

Provider accepts passing any value so you can paas object here and your values as properties.

<GameContext.Provider
 value={{ name: [name, setName], color: [color, setColor] }}
   >
  {props.children}
</GameContext.Provider>;

and where you are accessing in Child

 const { name, color } = React.useContext(GameContext);
 const [nameValue, setnameValue] = name;
 const [colorValue, setcolorValue] = color;
Zohaib
  • 967
  • 7
  • 12
12

useReducer is better suited to your case:

import React, { useState, useReducer, createContext } from 'react';

const initialState = {
  name: '',
  color: '',
  startgame: false
}

function reducer(state, action) {
  return { ...state, ...action };
}

const GameContext = createContext();

const GameProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <GameContext.Provider value={{ state, dispatch }}>
      {children}
    </GameContext.Provider>
  );
};

export { GameContext, GameProvider };

The child component:

import { GameContext } from '../../context/GameContext'; 

...

const { state: { name, color }, dispatch } = useContext(GameContext);
console.log(name);
console.log(color);

// event handler
const handleChangeName = (event) => {
  dispatch({ name: event.target.value });
}

const handleChangeColor = (event) => {
  dispatch({ color: event.target.value });
}
Fraction
  • 11,668
  • 5
  • 28
  • 48
  • With this implementation, if I want to reset state to its initial values how should I do it? – billysk Jun 17 '22 at 17:18
  • 1
    try `dispatch({ name: '', color: '', startgame: false })` or export/import the `initialState` and use it like this `dispatch(initialState)` or for only one value: `disptach({ color: initialState.color })`, you can also use Redux-like style: `dispatch({ type: 'RESET' })` and add a case in your reducer to reset state, check [this article](https://blog.logrocket.com/react-usereducer-hook-ultimate-guide/) for more info – Fraction Jun 17 '22 at 19:09
2
import React, { useState, createContext } from 'react';

const GameContext = createContext();

const GameProvider = ({ children }) => {
  const [state, setState] = useState({
       name: '',
       color: '',
       startgame: false
  });


  return (
    <GameContext.Provider value={{
         ...state, 
         setState: (data) => setState({...state, ...data})
       }}
    >
      {children}
    </GameContext.Provider>
  );
};

export { GameContext, GameProvider };
import { GameContext } from '../../context/GameContext';    
const {name, color, startGame, setState} = useContext(GameContext);
console.log(name);
// Updating name
setState({ name: 'test' });
abe312
  • 2,547
  • 25
  • 16
Niyas Nazar
  • 1,073
  • 13
  • 20
1

Pass them as an Object like Niyas Nazar commented.

Example:

CommandBarContext.tsx

import React, { createContext, useContext, useState } from 'react'

interface ComandBarState {
  newEnabled: boolean
  setNewEnabled: (state: boolean) => void
  copyEnabled: boolean
  setCopyEnabled: (state: boolean) => void
  deleteEnabled: boolean
  setDeleteEnabled: (state: boolean) => void
}

const commandBarContext = createContext<ComandBarState>({
  newEnabled: true,
  setNewEnabled: (state: boolean) => { },
  copyEnabled: false,
  setCopyEnabled: (state: boolean) => { },
  deleteEnabled: false,
  setDeleteEnabled: (state: boolean) => { },
})

export const CommandBarProvider = (props: any) => {
  const { children } = props

  const [newEnabled, setNewEnabled] = useState<boolean>(true)
  const [copyEnabled, setCopyEnabled] = useState<boolean>(false)
  const [deleteEnabled, setDeleteEnabled] = useState<boolean>(false)

  return (
    <commandBarContext.Provider value={{
      newEnabled, setNewEnabled,
      copyEnabled, setCopyEnabled,
      deleteEnabled, setDeleteEnabled
    }}>
      {children}
    </commandBarContext.Provider>)
}

export const useCommandNew = () => {
  const { newEnabled, setNewEnabled } = useContext(commandBarContext)
  return { newEnabled, setNewEnabled }
}

export const useCommandCopy = () => {
  const { copyEnabled, setCopyEnabled } = useContext(commandBarContext)
  return { copyEnabled, setCopyEnabled }
}

export const useCommandDelete = () => {
  const { deleteEnabled, setDeleteEnabled } = useContext(commandBarContext)
  return { deleteEnabled, setDeleteEnabled }
}

CommandBar.tsx

import React, { FunctionComponent } from 'react'

import {
  CommandBar as FluentCommandBar,
  ICommandBarItemProps,
  ICommandBarStyles
} from '@fluentui/react/lib/CommandBar'
import { Text } from '@fluentui/react/lib/Text'
import { Stack, StackItem } from '@fluentui/react/lib/Stack'

import {
  useCommandNew,
  useCommandCopy,
  useCommandDelete,
} from './CommandBarContext'
import { useTranslation } from 'react-i18next'

export interface CommandBarProps {
  title: string
  onCommandNew?: () => void,
  onCommandCopy?: () => void,
  onCommandDelete?: () => void,
}

export const CommandBar: FunctionComponent<CommandBarProps> = props => {
  const { title } = props

  const {
    onCommandNew = () => { },
    onCommandCopy = () => { },
    onCommandDelete = () => { },
  } = props

  const { newEnabled } = useCommandNew()
  const { copyEnabled } = useCommandCopy()
  const { deleteEnabled } = useCommandDelete()

  const { t } = useTranslation()

  const items: ICommandBarItemProps[] = [
    {
      key: 'commandNew',
      text: t('New'),
      disabled: !newEnabled,
      iconProps: { iconName: 'Add' },
      onClick: onCommandNew,
    },
    {
      key: 'commandCopy',
      text: t('Copy'),
      iconProps: { iconName: 'Copy' },
      disabled: !copyEnabled,
      onClick: onCommandCopy,
    },
    {
      key: 'commandDelete',
      text: t('Delete'),
      iconProps: { iconName: 'Delete' },
      disabled: !deleteEnabled,
      onClick: onCommandDelete,
    },
  ]

  return (
    <Stack horizontal tokens={{ childrenGap: 30 }}>
      <Text variant="xLarge" styles={{ root: { marginTop: 7 } }}>{title}</Text>
      <StackItem grow>
        <FluentCommandBar
          items={items}
          styles={commandBarStyles}
        />
      </StackItem>
    </Stack>
  )
}

const commandBarStyles: ICommandBarStyles = {
  root: {
    marginTop: 0,
    paddingLeft: 0,
    paddingRight: 0
  }
}

Use in parent component:

...

import {
  useCommandNew,
  useCommandCopy,
  useCommandDelete,
} from './CommandBarContext'

...

  const { setNewEnabled } = useCommandNew()
  const { setCopyEnabled } = useCommandCopy()
  const { setDeleteEnabled } = useCommandDelete()

...

  return (
    <CommandBarProvider>
      <Stack tokens={{ childrenGap: 5 }}>
        <CommandBar title="Locations" />
        <Table
          columns={columns}
          items={items}
          onSelection={onSelection}
          onItemInvoked={onItemInvoked}
        />
      </Stack>
      <Panel
        header="Location"
        columns={columns}
        items={selection}
        isPanelOpen={isPanelOpen}
        onCancel={onCancel}
        onSave={onSave}
      />
    </CommandBarProvider>
  )
}

...
Phierru
  • 219
  • 2
  • 5
0

Popping this one as I see so far no one as responded to OP's second question, which I find important.

I have another question, Somehow I feel this is a really stupid question, but why can't I do something like this in the GameContext.js?...

<GameContext.Provider value={[name, setName,color, setColor, startgame, setStartgame]}>

and get the values in the child component like this...

const [name, setName,color, setColor, startgame, setStartgame] = useContext(GameContext);

It might be something that's been updated since then, but I now see no reason why you can't do this. The context value object doesn't 'know' or 'care' about what's being passed inside of it, and upon destructuring at the child comp, you simply get its values regardless of what they are.

Hoping this would help someone.

tbger99
  • 196
  • 1
  • 4