Why can't I access the context's methods or properties - reactjs

I am trying to use React's context api to manage a global state. When I try to invoke contextual methods or access contextual proprties, I get errors saying "this.context.setUser function does not exist" or "undefined".
I have however been able to hard code values into the state of the context and retreive the hardcoded value.
Feed Context
import React, { Component } from 'react'
const FeedContext = React.createContext({
Feed: [],
user: '',
error: null,
setError: () => {},
clearError: () => {},
setFeed: () => {},
setUser: () => {}
})
export default FeedContext
export class FeedProvider extends Component {
state = {
feed: [],
error: null,
user: ''
};
setUser = user => {
this.setState({ user })
}
setFeed = Feed => {
this.setState({ Feed })
}
setError = error => {
console.error()
this.setState({ error })
}
clearError = () => {
console.log('context is accessed')
this.setState({ error: null })
}
render() {
const value = {
feed: this.state.feed,
error: this.state.error,
setError: this.setError,
clearError: this.clearError,
setFeed: this.setFeed,
setUser: this.setUser
}
return (
<FeedContext.Provider value={value}>
{this.props.children}
</FeedContext.Provider>
)
}
}
AccountPanel.js
import React from 'react';
import FeedContext from "../../contexts/FeedContext";
// functional component
class AccountPanel extends React.Component {
static contextType = FeedContext
renderUserInfo(){
const { user = [] } = this.context;
//this returns "undefined"
console.log(user.user)
//this returns "user.setUser() is not a function"
user.setUser('newUser')
//this returns ' '
this.context.setUser('y')
console.log(user)
}
render(){
return (
<section>
{ this.renderUserInfo() }
AccountPanel
</section>
)
}
}
export default AccountPanel;
I would like to be able to update the contextual state/user via this.context.setUser('newUser), then consume that value in my navbar component

File App.js
import React, { Component } from 'react';
import AccountPanel from "./components/AccountPanel";
import { FeedProvider } from './components/FeedContext';
class App extends Component {
render() {
return (
<div className="App">
<FeedProvider>
<AccountPanel />
</FeedProvider>
</div>
);
}
}
export default App;
File : FeedContext.js
import React, { Component } from 'react'
const FeedContext = React.createContext({
Feed: [],
user: '',
error: null,
setError: () => {},
clearError: () => {},
setFeed: () => {},
setUser: () => {}
})
export default FeedContext
export class FeedProvider extends Component {
constructor(props){
super(props);
this.state = {
feed: [],
error: null,
user: "11"
};
}
setUser = user => {
console.log(`setting usr fns called for username: ${user}`);
this.setState({ user });
}
setFeed = Feed => {
this.setState({ Feed })
}
setError = error => {
console.error()
this.setState({ error })
}
clearError = () => {
console.log('context is accessed')
this.setState({ error: null })
}
componentDidMount(){
console.log('FeedProvider:componentDidMount');
}
render() {
let value1 = {
Feed:this.state.feed,
user:this.state.user,
error:this.state.error,
setError:this.setError,
clearError:this.clearError,
setFeed:this.setFeed,
setUser:this.setUser
}
return (
<FeedContext.Provider value={value1}>
{this.props.children}
</FeedContext.Provider>
)
}
}
File : AccountPanel.js
import React from 'react';
import FeedContext from "./FeedContext";
// functional component
class AccountPanel extends React.Component {
static contextType = FeedContext
// return BlogPost component html/(JSX)
componentDidMount(){
console.log('AccountPanel:componentDidMount');
console.log(this.context);
const value = this.context;
//this returns "undefined"
console.log(value.user)
//this returns "user.setUser() is not a function"
console.log(value.setUser);
value.setUser('newUser');
}
render(){
const value = this.context;
console.log(`Value of new User is : ${value.user}`);
return (
<section>
AccountPanel
</section>
)
}
}
export default AccountPanel;
Hope This helps :)

Related

Accessing a function inside HOC

I just want to access componentDidMount in test file, how can I write a test case so that I can access componentDidMount? I just want to print hi in the console.
import React, { Component } from 'react';
export const MyComponent = (functionName, secondFunctionName) => (
WrappedComponent,
) => {
class WithWrapped extends Component {
constructor(props) {
super(props);
this.getEmployees = props.getEmployees;
this.clearEmployees = props.clearEmployees;
this.state = { user: 'test', loading: true };
this.refreshEmp = this.refreshEmp.bind(this);
}
componentDidMount() {
console.log('Hi')
}
UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
}
render() {
}
}
const mapStateToProps = (state) => {
return {
};
};
const mapDispatchToProps = (dispatch) => ({
});
return connect(mapStateToProps, mapDispatchToProps)(WithWrapped);
};

Props not displaying from fetch call

I am trying to display recipes and not sure if I have this setup correctly. I am pulling recipes from a rails api via get fetch request. At the moment nothing is displaying.
Here is my recipe container:
import React, { Component } from 'react'
import RecipeList from '../components/RecipeList'
import RecipeInput from '../components/RecipeInput'
import { connect } from 'react-redux'
import { postRecipes } from '../actions/postRecipes.js'
import { getRecipes } from '../actions/getRecipes'
class RecipeContainer extends Component{
constructor(props){
super(props)
}
componentDidMount(){
getRecipes()
}
render(){
return (
<div>
<RecipeInput postRecipes={this.props.postRecipes} />
<RecipeList getRecipes={this.props.recipes} />
</div>
)
}
}
const mapStateToProps = state =>({
recipes: state.recipes
})
const mapDispatchToProps = dispatch =>{
return{
postRecipes: (recipe) => dispatch(postRecipes(recipe)),
getRecipes: () => dispatch(getRecipes())
// deleteRecipe: id => dispatch({type: 'Delete_Recipe', id})
}
}
export default connect(mapStateToProps,mapDispatchToProps)(RecipeContainer)
Here is my get request....notice that I am returning my Recipe component here.
export const getRecipes = () => {
const BASE_URL = `http://localhost:10524`
const RECIPES_URL =`${BASE_URL}/recipes`
return (dispatch) => {
dispatch({ type: 'START_FETCHING_RECIPES_REQUEST' });
fetch(RECIPES_URL)
.then(response =>{ return response.json()})
.then(recipes => dispatch({ type: 'Get_Recipes', recipes }));
};
}
This is where I am trying to render the Recipe component from the get request
import React, {Component} from 'react';
// import { getRecipes } from '../actions/getRecipes.js';
import Recipe from './Recipe.js'
class RecipeList extends Component {
// componentDidMount(){
// getRecipes()
// }
render() {
return (
<div>
{this.props.recipes.map(recipe => (<Recipe recipe={recipe} key={recipe.id} />))}
</div>
)
}
}
export default RecipeList;
Edit: Added reducer
switch(action.type){
case 'Add_Recipe':
const recipe = {
name: action.name,
ingredients: action.ingredients,
chef_name: action.chef_name,
origin: action.origin,
category: action.category
}
return{
...state,
recipes: [...state.recipes, recipe],
}
case 'START_FETCHING_RECIPES_REQUEST':
return {
...state,
recipes: [...state.recipes],
requesting: true
}
case 'Get_Recipes':
return {
...state, recipes: action.recipes,
requesting: false
}
default:
return state
}
}
How can I correct this to make it work?
Issue
You are not passing the recipes to the RecipeList component that were fetched and presumably stored in state, and fed back to the UI via RecipeContainer.
Solution
Pass the recipe state from RecipeContainer to RecipeList as a prop. and then render/map the recipes from props.
RecipeContainer
class RecipeContainer extends Component{
componentDidMount() {
getRecipes();
}
render() {
return (
<div>
<RecipeInput postRecipes={this.props.postRecipes} />
<RecipeList getRecipes={this.props.recipes} /> // <-- pass recipe state
</div>
)
}
}
const mapStateToProps = state => ({
recipes: state.recipes,
});
const mapDispatchToProps = dispatch => {
return {
postRecipes: (recipe) => dispatch(postRecipes(recipe)),
getRecipes: () => dispatch(getRecipes())
}
};
RecipeList
class RecipeList extends Component {
render() {
const { recipes } = this.props;
return (
<div>
{recipes.map(recipe => (
<Recipe recipe={recipe} key={recipe.id} />
))}
</div>
);
}
}
The actual solution to this was I needed to have an explicit return in my mapStateToProp function.
Eg.
const mapStateToProp = state =>{
return {
recipes: state.recipes
}
}

React API this.props.message.map is not a function

I use the API to get Quotes, but I get an Unhandled Rejection error "(TypeError): this.props.message.map is not a function" https://ibb.co/dWqhjXK, I used debugger checked props saw that I get answers https://ibb.co/wM3mLb9 what then could be the reason? why am i getting this error? here is my code
Message.jsx
import React from 'react';
export class Message extends React.Component {
render() {
const list = this.props.message.map((item, index) => {
return <div key={index}>
<p>{item.content}</p>
</div>
});
return (
<div>
<p>{list}</p>
</div>
);
}
}
MessageContainer.js
import React from 'react';
import {connect} from "react-redux";
import {Message} from "./Message";
import {getMessageThunk} from "../../redux/message-reducer";
class MessageContainer extends React.Component {
componentDidMount() {
this.props.getMessageThunk();
}
render() {
return <Message {...this.props} />
}
}
const mapStateToProps = (state) => ({
message: state.message.users
})
export default connect(mapStateToProps, {getMessageThunk})(MessageContainer);
message-reducer.js
import {messageAPI} from "../Api/Api";
const MESSAGE = 'MESSAGE';
let initialState = {
users: [],
};
export const messageReducer = (state = initialState, action) => {
switch (action.type) {
case MESSAGE: {
return {...state, users: action.users}
}
default:
return state;
}
}
export const messageCreator = (users) => {
return {
type: MESSAGE, users
}
};
export const getMessageThunk = (users) => (dispatch) => {
messageAPI.getMessageAPI(users).then(response => {
dispatch(messageCreator(response.data));
})
}
Api.js
import * as axios from "axios";
const instance = axios.create({
withCredentials: true,
url: 'https://quotes15.p.rapidapi.com/quotes/random/',
headers: {
'x-rapidapi-key': 'bf490d72a0msh3bf159a87e0c27fp107a51jsn062ca1b9b00e',
'x-rapidapi-host': 'quotes15.p.rapidapi.com'
}
});
export const messageAPI = {
getMessageAPI() {
return instance.get(`https://quotes15.p.rapidapi.com/quotes/random/`)
},
};
Your message is not an array but an object. You do not have anything to iterate over there you could either simply return this.props.message.content but this is not a list.
const item = <p>{this.props.message.content}</p>

React TypeError: this.props.message.map is not a function

I am trying to display messages on the screen which I receive from api. I checked in the debugger (here are the screenshots https://ibb.co/gShTG8g https://ibb.co/dQmfwJp) where all the stages are going fine, but in the end I get an error called TypeError: this.props.message.map is not a function. Here is my actual code, link to api https://rapidapi.com/ajith/api/messages
Messages.jsx
import React from "react";
export class Messages extends React.Component {
render() {
const MessageList = this.props.message.map((item, index) => {
return <div key={index}>
<p>{item.Message}</p>
</div>
});
return(
<div>
{MessageList}
</div>
);
}
}
MessagesContainer.js
import React from 'react';
import { connect } from 'react-redux';
import {Messages} from "./Messages";
import {getMessagesThunk} from "../../Redux/users-reducer";
class MessagesContainer extends React.Component {
componentDidMount() {
this.props.getMessagesThunk();
}
render() {
return(
<>
<Messages {...this.props} />
</>
)
}
}
let mapStateToProps = (state) => ({
message: state.usersPage.messages
})
export default connect(mapStateToProps, {getMessagesThunk})(MessagesContainer);
users-reducer.js (Here is a part of my code)
let initialState = {
messages: [],
};
case MESSAGE:
return {
...state, messages: action.messages
}
export const getMessage = (messages) => ({type: MESSAGE, messages})
export const getMessagesThunk = (messages) => {
return (dispatch) => {
usersAPI.message(messages).then(response => {
if(response.data) {
dispatch(getMessage(response.data.Message))
}
})
}
}
Api.js
import axios from "axios";
const instance = axios.create({
params: {category: 'love'},
withCredentials: true,
headers: {
"API-KEY": "6bec01a1-e00c-42ca-ab9d-a03ad2e730cc",
'x-rapidapi-key': 'bf490d72a0msh3bf159a87e0c27fp107a51jsn062ca1b9b00e',
'x-rapidapi-host': 'ajith-messages.p.rapidapi.com'
}
})
export const usersAPI = {
message() {
return instance.get(`https://ajith-messages.p.rapidapi.com/getMsgs`)
},
}
This is because the message is not an array you can loop through. It's a simple string.

Why does my context not update state as expected?

The imageList is empty even though I have used this.context.setImageList to update the state to get the data from the API. I am confused why the state just didn't update. I have been spending a lot of time on this and haven't found the root cause. I'd appreciate if you can guide me through it. Thank you!
Create context and attach it in the Provider
import React, { Component } from 'react';
const ImageListContext = React.createContext({
imageList: [],
error: null,
loading: true,
setError: () => {},
clearError: () => {},
setImageList: () => {},
})
export default ImageListContext
export class ImageListProvider extends Component {
state = {
imageList: [],
error: null,
};
setImageList = imageList => {
this.setState({imageList})
}
setError = error => {
console.error(error)
this.setState({ error })
}
clearError = () => {
this.setState({ error: null })
}
render() {
const value = {
imageList: this.state.imageList,
error: this.state.error,
setError: this.setError,
clearError: this.clearError,
setImageList: this.setImageList,
}
return (
<div>
{this.loading ? <div>Loading Images...</div> :
<ImageListContext.Provider value={value}>
{this.props.children}
</ImageListContext.Provider>}
</div>
)
}
}
Use Context to update imageList array with the data from API and get the data out of the array to display it
import React, { Component } from 'react'
import ImageApiService from '../../services/image-api-service'
import { Section } from '../../components/Utils/Utils'
import ImageListItem from '../../components/ImageListItem/ImageListItem'
import './ImageListPage.css'
import ImageListContext from '../../contexts/ImageListContext'
export default class ImageListPage extends Component {
static contextType = ImageListContext;
componentDidMount() {
//this calls the image API to get all images!
ImageApiService.getImages()
.then(resJson => this.context.setImageList(resJson))
.catch(error => console.log(error))
}
setError = error => {
console.error(error)
this.setState({ error })
}
clearError = () => {
this.setState({ error: null })
}
renderImages() {
const { imageList=[] } = this.context;
console.log(imageList)
return imageList.map(image =>
<ImageListItem
key={image.id}
image={image}
/>
)
}
render() {
return (
<Section list className='ImageListPage'>
{this.context.error
? <p className='red'>There was an error, try again</p>
: this.renderImages()}
</Section>
)
}
}

Resources