I'm fairly new to Laravel and React. I have 3 tables called students, users and projects. Each project has 1 student, 1 supervisor and 2 examiners (supervisors and examiners are users) assigned to it. I want to try to fetch the names of the student and user from the respective tables and display them.
My Project.php
protected $with =['projectStudent', 'projectSupervisor', 'projectExaminer1', 'projectExaminer2'];
public function projectStudent()
{
return $this->belongsTo('App\Models\Student', 'student_id', 'student_id');
}
public function projectSupervisor()
{
return $this->belongsTo('App\Models\User', 'supervisor_id', 'id');
}
public function projectExaminer1()
{
return $this->belongsTo('App\Models\User', 'examiner1_id', 'id');
}
public function projectExaminer2()
{
return $this->belongsTo('App\Models\User', 'examiner2_id', 'id');
}
My ProjectController.php
public function index()
{
return ProjectResource::collection(
Project::query()->with('projectStudent')->orderBy('id', 'desc')->paginate(5)
);
}
My Projects.jsx
const [projects, setProjects] = useState([]);
const [paginate, setPaginate] = useState(1);
const [loading, setLoading] = useState(false);
const {setNotification} = useStateContext();
useEffect(() => {
getProjects();
}, [paginate])
const handlePaginate = (direction) => {
setPaginate(prevPage => prevPage + direction)
}
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
const getProjects = () => {
setLoading(true);
axiosClient.get(`/projects?page=${paginate}`)
.then(({data}) => {
setLoading(false);
setProjects(data.data);
})
.catch(() => {
setLoading(false);
})
}
I want to get name of the student and the names of supervisor and examiner but project.projectStudent.name returns undefined.
When I try to console.log(JSON.stringify(data.data, null, 2)) the data, I get
[
{
"id": 1,
"title": "Stock Market Prediction",
"student_id": "SW0102469",
"supervisor_id": 18,
"examiner1_id": 12,
"examiner2_id": 33
}
]
And this is my projectResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ProjectResource extends JsonResource
{
public static $wrap = false;
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'student_id' => $this->student_id,
'supervisor_id' => $this->supervisor_id,
'examiner1_id' => $this->examiner1_id,
'examiner2_id' => $this->examiner2_id,
// 'start_date' => $this->start_date->format('Y-m-d'),
// 'end_date' => $this->end_date->format('Y-m-d'),
// 'duration' => $this->duration,
// 'status' => $this->status,
];
}
}
Related
I'm using React and Laravel for the creation of a list.
In the add button I have the following
function handleAdd() {
const newList = list.concat({ name, id: uuidv4(), value });
setList(newList);
setName("");
setValue("");
}
const onAddSocial = (e) => {
e.preventDefault();
const test = {
value: value,
name: name,
};
axios.post('http://localhost:8000/api/social', test)
.then((res) => {
toast(<div class="text-primary font-weight-bolder">Social link added successfully!</div>,{ delay: 0 });
console.log(res)
}).catch((error) => {console.log(error)
})
}
And the button to trigger both functions for the creation :
<Button
onClick={(e) => {
props.handleAdd();
props.onAddSocial(e);
}}
>
The element is created, but in the datatabse the id receive an incremented value. How do I pass the uuidv4() of the created element instead ?
The store controller :
public function store(Request $request)
{
$data = new Social();
$data->value = $request->value;
$data->name = $request->name;
$data->save();
}
You have to use following code
In your Social Model you have to use like this
public $incrementing = false;
protected $keyType = 'string';
public static function boot(){
parent::boot();
static::creating(function ($social) {
$social->id = Str::uuid(36);
});
}
Add this line in header section
use Illuminate\Support\Str;
In your database\migration social file add below code also
public function up()
{
Schema::create('socials', function (Blueprint $table) {
$table->uuid('id', 36)->primary();
$table->timestamps();
});
}
I currently have a real problem. I want to redirect my user to the right conversation or publication when they press a notification.
All the code works, but I have the same problem all the time: the redirection happens before the action is completed, which results in a nice error telling me that the item is "null".
If I redirect to a publication with a new comment, it shows the publication, but the comments load one or two seconds after being redirected.
How is it possible to wait for the end of an action before redirecting?
Thanks a lot
My action (with Redux Thunk)
export const fetchPublications = token => {
return async dispatch => {
await axios
.get(`/articles?token=${token}`)
.then(response => {
const articles = response.data.articles;
const groups = response.data.groups;
const groupPosts = response.data.groupPosts;
const comments = response.data.comments;
const loadedArticles = [];
const loadedGroups = [];
const loadedGroupPosts = [];
const loadedComments = [];
for (const key in articles) {
loadedArticles.push(
new Article(
articles[key].id,
articles[key].title,
articles[key].content,
articles[key].description,
articles[key].cover,
articles[key].dateCreation,
articles[key].creatorPhoto,
articles[key].creatorFirstName,
articles[key].creatorLastName,
articles[key].creatorId,
articles[key].slug,
articles[key].isOnline,
articles[key].isForPro,
'article',
),
);
}
for (const key in groups) {
loadedGroups.push(
new Group(
groups[key].id,
groups[key].name,
groups[key].icon,
groups[key].cover,
groups[key].description,
groups[key].isPublic,
groups[key].isOnInvitation,
groups[key].dateCreation,
groups[key].slug,
groups[key].safeMode,
groups[key].isOnTeam,
groups[key].role,
groups[key].isWaitingValidation,
'group',
),
);
}
for (const key in groupPosts) {
loadedGroupPosts.push(
new GroupPost(
groupPosts[key].id,
groupPosts[key].content,
groupPosts[key].dateCreation,
groupPosts[key].lastModification,
groupPosts[key].creatorPhoto,
groupPosts[key].creatorFirstName,
groupPosts[key].creatorLastName,
groupPosts[key].creatorId,
groupPosts[key].onGroupId,
groupPosts[key].groupName,
groupPosts[key].groupIcon,
'groupPost',
groupPosts[key].liked,
groupPosts[key].likesCounter,
groupPosts[key].commentsCounter,
),
);
}
for (const key in comments) {
loadedComments.push(
new Comment(
comments[key].id,
comments[key].content,
comments[key].dateCreation,
comments[key].lastModification,
comments[key].creatorPhoto,
comments[key].creatorFirstName,
comments[key].creatorLastName,
comments[key].creatorId,
comments[key].onPostId,
),
);
}
dispatch({
type: FETCH_PUBLICATIONS,
articles: loadedArticles,
groups: loadedGroups,
groupPosts: loadedGroupPosts,
comments: loadedComments,
});
})
.catch(error => {
console.log(error);
throw new Error('Une erreur est survenue.');
});
};
};
My notification handler
const handleNotificationResponse = async response => {
if (response.actionIdentifier === 'expo.modules.notifications.actions.DEFAULT') {
try {
if (response.notification.request.content.data.discussionId) {
if (isAuth) {
const discussionId =
response.notification.request.content.data.discussionId;
dispatch(messengerActions.fetchMessenger(userToken));
const item = messages.filter(
message => message.id == discussionId,
);
navigationRef.current?.navigate('MessengerApp', {
screen: 'Discussion',
params: { item: item[0] },
});
}
} else if (response.notification.request.content.data.groupPostId) {
if (isAuth) {
const groupPostId =
response.notification.request.content.data.groupPostId;
dispatch(newsfeedActions.fetchPublications(userToken));
const item = groupPosts.filter(
groupPost => groupPost.id == groupPostId,
);
navigationRef.current?.navigate('App', {
screen: 'Comments',
params: {
item: item[0],
},
});
}
}
} catch (err) {}
} else {
}
};
I want to add an All Option to my existing select box.
Select box is creating with some API data. With the API data set I want to add an ALL option above.
This is my code.
const useChemicals = () => {
const [data, setData]: any = useState([]);
useEffect(() => {
const getChemicalsData = async () => {
try {
const results = await searchApi.requestChemicalsList();
if (results.data) {
let groupCount = 0;
const chemList: any = [];
results.data.data.chemicals.map((chemical: any, index: number) => {
if (chemical.key === '') {
chemList.push({
label: chemical.value,
options: [],
});
}
});
results.data.data.chemicals.map((chemical: any, index: number) => {
if (chemical.key === '') {
if (index > 1) {
groupCount += 1;
}
} else {
chemList[groupCount].options.push({
label: chemical.value,
value: chemical.key,
});
}
});
setData([...chemList]);
}
} catch (e) {}
};
getChemicalsData();
}, []);
return data && data;
};
export default useChemicals;
How can I add this. Please help me, I am new to React.
So i want to subscribe to multiple events for the current logged user.
I've extracted the subscriptions to a separate function that update my logged user state from inside and returns an array of subscriptions.
Now i wanted to know is there a different / better way of doing this ?
Is this the correct / recommended way of approaching this problem ?
Current implementation
export const subscribeToCurrentUserUpdates = (setLoggedUser) => {
const friendRequestObserver$ = apolloClient.subscribe(
{ query: queries.NEW_FRIEND_REQUEST },
);
const followersUpdatesObserver$ = apolloClient.subscribe(
{ query: queries.FOLLOWERS_UPDATES },
);
const acceptedFriendRequestObserver$ = apolloClient.subscribe(
{ query: queries.ACCEPTED_FRIEND_REQUEST },
);
const friendRequestSubscription = friendRequestObserver$.subscribe({
next: ({ data: { newFriendRequest } }) => {
Alert.success(`${newFriendRequest.username} just sent you a friend request`);
setLoggedUser((loggedUser) => {
loggedUser.incomingFriendRequests.unshift(newFriendRequest._id);
});
},
error: err => console.error(err),
});
const followersUpdatesSubscription = followersUpdatesObserver$.subscribe({
next: ({ data: { followersUpdates: { follower, isFollow } } }) => {
if (isFollow) {
Alert.success(`${follower.username} is now following you`);
}
setLoggedUser((loggedUser) => {
isFollow
? loggedUser.followers.unshift(follower._id)
: loggedUser.followers.splice(loggedUser.followers.indexOf(follower._id), 1);
});
},
error: err => console.error(err),
});
const acceptedFriendRequestSubscription = acceptedFriendRequestObserver$.subscribe({
next: ({ data: { acceptedFriendRequest: newFriend } }) => {
Alert.success(`${newFriend.username} just accepted your friend request!`);
setLoggedUser((loggedUser) => {
loggedUser.friends.push(newFriend._id);
loggedUser.sentFriendRequests.splice(
loggedUser.sentFriendRequests.indexOf(newFriend._id), 1,
);
});
},
error: err => console.error(err),
});
return [
friendRequestSubscription,
followersUpdatesSubscription,
acceptedFriendRequestSubscription,
];
};
The way i subscribe from my component
const App = () => {
const currentUserSubscriptionRef = useRef();
useEffect(() => {
if (loggedUser && !currentUserSubscriptionRef.current) {
currentUserSubscriptionRef.current = subscribeToCurrentUserUpdates(
setLoggedUser,
);
}
if (!loggedUser && currentUserSubscriptionRef.current) {
currentUserSubscriptionRef.current.forEach((subscription) => {
subscription.unsubscribe();
});
currentUserSubscriptionRef.current = null;
}
}, [loggedUser, setLoggedUser]);
}
I need to modify my state and I am unsure how to do it correctly.
My account property in my state looks something like this:
{
"account":{
"id":7,
"categories":[
{
"id":7,
"products":[
{
"productId":54
}
]
},
{
"id":9,
"products":[
{
"productId":89
}
]
}
]
}
}
My action dispatches the following:
dispatch({
type: Constants.MOVE_PRODUCT,
productId: 54,
sourceCategoryId: 7,
targetCategoryId: 9
});
Now my reducer skeleton is:
const initialState = {
account: null,
};
const accounts = (state = initialState, action) => {
switch (action.type) {
case Constants.MOVE_PRODUCT:
/*
action.productId
action.sourceCategoryId
action.targetCategoryId
*/
const sourceCategoryIndex = state.account.categories.findIndex((category) => { return category.id === action.sourceCategoryId; });
const sourceCategory = state.account.categories[sourceCategoryIndex];
const targetCategoryIndex = state.account.categories.findIndex((category) => { return category.id === action.targetCategoryId; });
const targetCategory = state.account.categories[targetCategoryIndex];
// ??
return {...state};
}
}
export default accounts;
I am confused, if I update the state directly inside of the switch block, is that wrong?
Does it have to be a one-liner update that does the mutation in-place or as long as I do it in the switch block it is fine?
Update
From the action, I need to remove the productId from the sourceCategoryId and add it to the targetCategoryId inside of the account state object.
Yes, you should not be doing state.foo = 'bar' in your reducer. From the redux docs:
We don't mutate the state. We create a copy with Object.assign(). Object.assign(state, { visibilityFilter: action.filter }) is also wrong: it will mutate the first argument. You must supply an empty object as the first parameter. You can also enable the object spread operator proposal to write { ...state, ...newState } instead.
So your reducer could look like
function accountsReducer (state = initialState, { sourceCategoryId, productId }) {
const targetProduct = state.categories
.find(({ id }) => id === sourceCategoryId)
.products
.find(({ id }) => id === productId);
switch (action.type) {
case Constants.MOVE_PRODUCT:
return {
...state,
categories: state.categories.reduce((acc, cat) => {
return cat.id !== sourceCategoryId
? {
...acc,
cat: { ...cat, products: cat.products.filter(({ id }) => id !== productId) }
}
: {
...acc,
cat: { ...cat, products: [...cat.products, targetProduct] }
}
}, {});
};
}
}
But this a pain...you should try to normalize your data into a flat array.
// first, let's clean up the action a bit
// type and "payload". I like the data wrapped up in a bundle with a nice
// bow on it. ;) If you don't like this, just adjust the code below.
dispatch({
type: Constants.MOVE_PRODUCT,
payload: {
product: { productId: 54 }
sourceCategoryId: 7,
targetCategoryId: 9
}
});
// destructure to get our id and categories from state
const { id, categories } = state
// map the old categories to a new array
const adjustedCategories = categories.map(cat => {
// destructure from our payload
const { product, sourceCategoryId, targetCategoryId } = action.payload
// if the category is the "moving from" category, filter out the product
if (cat.id === sourceCategoryId) {
return { id: cat.id, products: [...cat.products.filter(p => p.productId !== product.productId)
}
// if the category is our "moving to" category, use the spread operator and add the product to the new array
if (cat.id === targetCategoryId) {
return { id: cat.id, products: [...cat.products, product] }
}
)
// construct our new state
return { id, categories: adjustedCategories }
This solution keeps the function pure and should give you what you want. It's not tested, so may not be perfect.
You could take the following approach:
const accounts = (state = initialState, action) => {
switch (action.type) {
case Constants.MOVE_PRODUCT:
// Extract action parameters
const { productId, sourceCategoryId, targetCategoryId } = action
// Manually "deep clone" account state
const account = {
id : state.account.id,
categories : state.account.categories.map(category => ({
id : category.id,
products : category.products.map(product => ({ productId : product.productId })
}))
}
// Extract source and target categories
const sourceCategory = account.categories.find(category => category.id === sourceCategoryId);
const targetCategory = account.categories.find(category => category.id === targetCategoryId);
if(sourceCategory && targetCategory) {
// Find product index
const index = sourceCategory.products.findIndex(product => (product.productId === action.productId))
if(index !== -1) {
const product = sourceCategory.products[index]
// Remove product from source category
sourceCategory.products.splice(index, 1)
// Add product to target category
targetCategory.products.splice(index, 0, product)
}
}
return { account };
}
}
Here is the ugly solution :)
const accounts = (state = initialState, action) => {
switch (action.type) {
case Constants.MOVE_PRODUCT:
const sourceCategoryIndex = state.account.categories.findIndex(
el => el.id === action.sourceCategoryId
);
const targetCategoryIndex = state.account.categories.findIndex(
el => el.id === action.targetCategoryId
);
const sourceCategory = state.account.categories.find(
el => el.id === action.sourceCategoryId
);
const targetCategory = state.account.categories.find(
el => el.id === action.targetCategoryId
);
const itemToMove = sourceCategory.products.find(
el => el.productId === action.productId
);
const newSourceCategory = {
...sourceCategory,
products: sourceCategory.products.filter(
el => el.productId !== action.productId
)
};
const newTargetCategory = {
...targetCategory,
products: [...targetCategory.products, itemToMove]
};
const newCategories = Object.assign([], state.account.categories, {
[sourceCategoryIndex]: newSourceCategory,
[targetCategoryIndex]: newTargetCategory
});
return { ...state, account: { ...state.account, categories: newCategories } };
}
};
Phew :) As a learner it's quite good for me :) But, I like #Daniel Lizik's approach, using reduce.
Here is the working example:
const action = {
productId: 54,
sourceCategoryId: 7,
targetCategoryId: 9,
}
const state = {
"account":{
"id":7,
"categories":[
{
"id":7,
"products":[
{
"productId":54,
},
{
"productId":67,
},
]
},
{
"id":9,
"products":[
{
"productId":89,
}
]
}
]
}
};
const sourceCategoryIndex = state.account.categories.findIndex( el => el.id === action.sourceCategoryId );
const targetCategoryIndex = state.account.categories.findIndex( el => el.id === action.targetCategoryId );
const sourceCategory = state.account.categories.find( el => el.id === action.sourceCategoryId );
const targetCategory = state.account.categories.find( el => el.id === action.targetCategoryId );
const itemToMove = sourceCategory.products.find( el => el.productId === action.productId );
const newSourceCategory = {...sourceCategory, products: sourceCategory.products.filter( el => el.productId !== action.productId ) };
const newTargetCategory = { ...targetCategory, products: [ ...targetCategory.products, itemToMove ] };
const newCategories = Object.assign([], state.account.categories, { [sourceCategoryIndex]: newSourceCategory,
[targetCategoryIndex]: newTargetCategory }
);
const newState = { ...state, account: { ...state.account, categories: newCategories } };
console.log( newState );